<!-- 树状选择器 -->
<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted } from 'vue'
// 传入props
interface Props {
  modelValue: string | string[] | undefined | null
  options: any
  treeProps?: any
  nodeKey?: string
  clearable?: boolean
  multiple?: boolean
  disabled?: boolean
  placeholder?: string
  searchPlaceholder?: string
  showSearch?: boolean
  collapseTags?: boolean
  isLeafNode?: boolean
  checkStrictly?: boolean
  disabledLey?: string
  placement?: string
  lineTree?: boolean
}
const emit = defineEmits(['removeTag', 'node-change', 'update:modelValue', 'clear'])
const props = withDefaults(defineProps<Props>(), {
  options: [],
  treeProps: {
    children: 'children', // 子级字段
    label: 'mc', // 展示字段
  },
  nodeKey: 'dm',
  clearable: true,
  multiple: false,
  disabled: false,
  placeholder: '请选择',
  searchPlaceholder: '请输入关键字',
  showSearch: true,
  collapseTags: true,
  isLeafNode: true,
  checkStrictly: true,
  placement: 'bottom-start',
  lineTree: false,
})
const disabledFn = (data: any) => {
  return !!data.children?.length
}
// 下拉框Option的值，设置为undefined，防止选中
const selectOptionValue = ref<any>(undefined)
// 下拉框选中回显值
const selectLabel = ref<any>('')
const selectValue = ref<any>('')
// 树数据搜索内容
const treeFilterText = ref<any>('')
// 默认展开
const defaultExpandedKeys = ref<any[]>([])

const reloadTreeCheck = ref<boolean>(false)

/**
 * 计算属性
 */
// select 下拉框自定义的类名-可自行修改
const popperClass = computed(() => {
  const classNames = ['custom-tree-select-popper']
  if (props.showSearch) {
    classNames.push('custom-tree-select-search')
  }
  return classNames.join(' ')
})
// 下拉框实例
const treeSelectRef = ref()
// 树结构实例
const optionTreeRef = ref()

onMounted(() => {
  if (props.modelValue) {
    if (!reloadTreeCheck.value && props.options?.length > 0) {
      handleSetTreeCheck(props.modelValue)
    }
  } else {
    const value = props.multiple ? [] : ''
    selectValue.value = value
    selectLabel.value = value

    // 清空节点依然选中修复（清空选中节点，收缩节点）
    nextTick(() => {
      handleSetTreeNode(value)
      handleTreeExpandKeys([])
      const allNodes = optionTreeRef.value.store.nodesMap
      for (const key in allNodes) {
        allNodes[key].expanded = false
      }
    })
  }
})

/**
 * watch监听
 */
watch(
  () => props.modelValue,
  (newValue) => {
    if (newValue) {
      if (!reloadTreeCheck.value && props.options?.length > 0) {
        handleSetTreeCheck(newValue)
      }
    } else {
      const value = props.multiple ? [] : ''
      selectValue.value = value
      selectLabel.value = value

      // 清空节点依然选中修复（清空选中节点，收缩节点）
      nextTick(() => {
        handleSetTreeNode(value)
        handleTreeExpandKeys([])
        const allNodes = optionTreeRef.value.store.nodesMap
        for (const key in allNodes) {
          allNodes[key].expanded = false
        }
      })
    }
  },
  { deep: true },
)

watch(treeFilterText, (newValue) => {
  setTimeout(() => {
    handleInputChange(newValue)
  }, 300)
})

watch(
  () => props.options,
  (newValue) => {
    if (newValue && newValue.length > 0 && !reloadTreeCheck.value && props.modelValue) {
      handleSetTreeCheck(props.modelValue)
    }
  },
  { deep: true },
)

onMounted(() => {
  // 重新加载树结构选中 false-加载 true-不加载
  reloadTreeCheck.value = false
})

/***
 * 方法
 */
/** 多选模式下，点击移除单个tag */
const handleSelectRemoveTag = (tag: any) => {
  reloadTreeCheck.value = false
  // const { selectValue } = this
  if (isArray(selectValue.value)) {
    selectValue.value.shift()
  }
  emit('removeTag', tag)
}
/** select框的清除按钮 */
const handleSelectClear = () => {
  reloadTreeCheck.value = false
  const value = props.multiple ? [] : ''
  treeFilterText.value = ''
  selectValue.value = value
  selectLabel.value = value
  if (props.multiple) {
    emit('node-change', value, [])
    emit('update:modelValue', [])
  } else {
    emit('update:modelValue', '')
    emit('node-change', value, '')
  }
  emit('clear')
}
/** 树节点 点击时 */
const handleNodeClick = (node: any) => {
  const { treeProps, multiple, nodeKey, isLeafNode } = props
  if (multiple) return
  // if (node.children && node.children.length > 0) return // 父节点不能选中
  if (isLeafNode && node.children && node.children.length > 0) return
  // 自定义是否可以选中
  if (!isLeafNode && props.disabledLey && !node[props.disabledLey]) return
  selectValue.value = node[nodeKey]
  selectLabel.value = node[treeProps.label]
  // emit('node-change', selectValue.value, node)
  emit('update:modelValue', selectValue.value)
  // 下拉框失去焦点，隐藏下拉面板
  treeSelectRef.value.blur()
  // treeFilterText.value = ''
}
// /** 树节点 复选框选中时 */
const handleTreeCheck = (node: any, values: any) => {
  const { treeProps } = props
  const { checkedKeys, checkedNodes } = values
  selectValue.value = checkedKeys
  const lableValues = checkedNodes.map((nodeItem: { [x: string]: any }) => {
    return nodeItem[treeProps.label]
  })
  selectLabel.value = lableValues
  // emit('node-change', checkedKeys, checkedNodes)
  emit('update:modelValue', selectValue.value)
}
/** 设置回显数据 */
const handleSetTreeNode = (value: any) => {
  const { treeProps } = props
  selectValue.value = value
  if (isArray(value)) {
    // 多选
    selectLabel.value = value.map((item: any) => {
      const treeNode = optionTreeRef.value.getNode(item)
      return treeNode?.data[treeProps.label] || ''
    })

    if (selectValue.value.length === 0) {
      emit('node-change', selectValue.value, null)
    } else {
      // 获取选中节点
      const checkedNodes = optionTreeRef.value.getCheckedNodes()
      emit('node-change', selectValue.value, checkedNodes)
    }
  } else {
    // 单选
    const treeNode = optionTreeRef.value.getNode(value)
    if (!treeNode) {
      return
    }
    selectLabel.value = treeNode.data[treeProps.label]
    if (!selectValue.value) {
      emit('node-change', selectValue.value, null)
    } else {
      // 获取选中节点
      const node = optionTreeRef.value.getCurrentNode()
      emit('node-change', selectValue.value, node || treeNode.data)
    }
  }
}
/** 处理数据树结构展开，并处理选中效果 */
const handleTreeExpandKeys = (value: any) => {
  const { multiple } = props
  if (isArray(value) && multiple) {
    optionTreeRef.value.setCheckedKeys(value)
    value.forEach((item: any) => {
      const treeNode = optionTreeRef.value.getNode(item)
      if (treeNode && treeNode.parent) {
        setTreeExpandKeys(treeNode.parent)
      }
    })
  } else {
    optionTreeRef.value.setCurrentKey(value)
    const treeNode = optionTreeRef.value.getNode(value)
    if (treeNode && treeNode.parent) {
      setTreeExpandKeys(treeNode.parent)
    }
  }
}
/** 处理树结构父级展开 */
const setTreeExpandKeys = (node: any) => {
  node.expanded = true
  if (node.parent) {
    setTreeExpandKeys(node.parent)
  }
}
/** 搜索树节点 */
const filterTreeNode = (value: any, data: any) => {
  if (!value) {
    // if (node.expanded) node.expanded = false
    return true
  }
  const label = props.treeProps.label
  return data[label].indexOf(value) !== -1
}
/** 设置树结构回显选中 */
const handleSetTreeCheck = (value: any) => {
  if (!reloadTreeCheck.value && value) {
    nextTick(() => {
      handleSetTreeNode(value)
      handleTreeExpandKeys(value)
      // reloadTreeCheck.value = true
    })
  }
}
/** 搜索框中按下回车失去焦点触发 */
const handleInputChange = (value: any) => {
  optionTreeRef.value.filter(value)
}

/**
 * 判断数据类型是否为数组
 */
const isArray = (arg: any) => {
  if (typeof Array.isArray === 'undefined') {
    return Object.prototype.toString.call(arg) === '[object Array]'
  }
  return Array.isArray(arg)
}
</script>
<template>
  <div class="tree-select">
    <el-select
      ref="treeSelectRef"
      :clearable="clearable"
      :model-value="selectLabel"
      :multiple="multiple"
      :disabled="disabled"
      :popper-append-to-body="false"
      :placeholder="placeholder"
      :placement="placement"
      :popper-class="popperClass"
      :collapse-tags="collapseTags"
      @remove-tag="handleSelectRemoveTag"
      @clear="handleSelectClear"
      style="width: 100%"
    >
      <div class="tree-select-search" v-if="showSearch">
        <el-input placeholder="请输入关键字" v-model="treeFilterText" clearable></el-input>
      </div>
      <el-option :value="selectOptionValue || undefined" class="tree-select-option-item">
        <div class="tree-select-tree-body">
          <!-- empty-text="加载中，请稍后......" -->
          <el-tree
            :class="{ 'line-tree': lineTree }"
            id="tree-option"
            ref="optionTreeRef"
            highlight-current
            :show-checkbox="multiple"
            :data="options"
            :props="{
              ...treeProps,
              disabled: isLeafNode ? disabledFn : false,
            }"
            :check-on-click-node="multiple"
            :node-key="nodeKey"
            :check-strictly="checkStrictly"
            :filter-node-method="filterTreeNode"
            :default-expanded-keys="defaultExpandedKeys"
            @node-click="handleNodeClick"
            @check="handleTreeCheck"
          >
          </el-tree>
        </div>
      </el-option>
    </el-select>
  </div>
</template>

<style lang="scss">
.custom-tree-select-search {
  .el-select-dropdown__list {
    padding: 0;
  }
}
.custom-tree-select-popper {
  .el-scrollbar {
    .el-select-dropdown__wrap {
      max-height: 365px !important;
    }
    .el-scrollbar__bar.is-vertical {
      z-index: 3;
    }
  }
}
</style>
<style lang="scss" scoped>
.tree-select {
  width: 100%;
}
.tree-select-search {
  position: sticky;
  top: 0;
  z-index: 2;
  display: block;
  padding: 6px;
  background: #fff;
}
.tree-select-option-item {
  background: #fff;
  overflow: scroll;
  height: 200px;
  overflow-x: hidden;
}
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  height: auto;
  min-height: 200px;
  padding: 0;
  overflow: hidden;
}
.el-select-dropdown__item,
.el-select-dropdown__item:hover {
  background-color: #fff !important;
}

:deep(.line-tree) {
  .el-tree-node {
    position: relative;
    padding-left: 0;
  }
  .el-tree-node__children {
    padding-left: 19px;
  }
  .el-tree-node :last-child:before {
    height: 16px;
  }
  .el-tree > .el-tree-node:before {
    border-left: none;
  }
  .el-tree-node:before {
    content: '';
    left: 0px;
    position: absolute;
    right: auto;
    border-width: 1px;
    border-left: 1px dashed #4386c6;
    bottom: 0px;
    height: 100%;
    top: 0px;
    width: 1px;
  }
  .el-tree-node:after {
    content: '';
    left: 0;
    position: absolute;
    right: auto;
    border-width: 1px;
    border-top: 1px dashed #4386c6;
    height: 16px;
    top: 16px;
    width: 30px;
  }
}
.tree-select-tree-body {
  :deep(.el-tree > .el-tree-node:after) {
    border-top: none;
  }
  :deep(.el-tree > .el-tree-node:before) {
    border-left: none;
  }
}
</style>
